///////////////////////////////////////////////////////////////////////////////
// $Id$
///////////////////////////////////////////////////////////////////////////////
//  This file is subject to the terms of the GNU General Public License as
//  published by the Free Software Foundation.  A copy of this license is
//  included with this software distribution in the file COPYING.  If you
//  do not have a copy, you may obtain a copy by writing to the Free
//  Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details
///////////////////////////////////////////////////////////////////////////////

/**
 * @file CPU.cpp CPU Detection
 *  This file contains #define directives that control compilation of CPU-specific
 *  code, mostly deinterlacing functions.  Turning these directives on requires
 *  that you have Microsoft's _T("Processor Pack") patch installed on your build system.
 *  The Processor Pack is available from Microsoft for free:
 *
 *  http://msdn.microsoft.com/vstudio/downloads/ppack/
 *
 *  Note that compiling the code to use a processor-specific feature is safe even
 *  if your PC doesn't have the feature in question; dTV detects processor types
 *  at startup and sets flags in the global _T("CpuFeatureFlags") (see cpu.h for
 *  the list of flags) which the code uses to determine whether or not to use
 *  each feature.
 */

#include <stdafx.h>
#include "cpu.h"

// Symbolic constants for feature flags in CPUID standard feature flags

#define CPUID_STD_FPU          0x00000001
#define CPUID_STD_VME          0x00000002
#define CPUID_STD_DEBUGEXT     0x00000004
#define CPUID_STD_4MPAGE       0x00000008
#define CPUID_STD_TSC          0x00000010
#define CPUID_STD_MSR          0x00000020
#define CPUID_STD_PAE          0x00000040
#define CPUID_STD_MCHKXCP      0x00000080
#define CPUID_STD_CMPXCHG8B    0x00000100
#define CPUID_STD_APIC         0x00000200
#define CPUID_STD_SYSENTER     0x00000800
#define CPUID_STD_MTRR         0x00001000
#define CPUID_STD_GPE          0x00002000
#define CPUID_STD_MCHKARCH     0x00004000
#define CPUID_STD_CMOV         0x00008000
#define CPUID_STD_PAT          0x00010000
#define CPUID_STD_PSE36        0x00020000
#define CPUID_STD_MMX          0x00800000
#define CPUID_STD_FXSAVE       0x01000000
#define CPUID_STD_SSE          0x02000000
#define CPUID_STD_SSE2         0x04000000

// Symbolic constants for feature flags in CPUID extended feature flags

#define CPUID_EXT_3DNOW        0x80000000
#define CPUID_EXT_AMD_3DNOWEXT 0x40000000
#define CPUID_EXT_AMD_MMXEXT   0x00400000


UINT CpuFeatureFlags = 0;
//---------------------------------------------------------------------------
// Get features of our CPU - modified from sample code from AMD & Intel

void CPU_SetupFeatureFlag(void)
{
#ifndef _M_AMD64
   UINT signature = 0;
   char vendor[13]        = "UnknownVendr";  // Needs to be exactly 12 chars

   // Define known vendor strings here

   char vendorAMD[13]     = "AuthenticAMD";  // Needs to be exactly 12 chars
   char vendorIntel[13]   = "GenuineIntel";  // Needs to be exactly 12 chars

   // Step 1: Check if processor has CPUID support. Processor faults
   // with an illegal instruction exception if the instruction is not
   // supported. This step catches the exception and immediately returns
   // with feature tstring bits with all 0s, if the exception occurs.
    __try
    {
        __asm xor    eax, eax
        __asm xor    ebx, ebx
        __asm xor    ecx, ecx
        __asm xor    edx, edx
        __asm cpuid
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return;
    }

    CpuFeatureFlags |= FEATURE_CPUID;

    _asm
    {
         // Step 2: Check if CPUID supports function 1 (signature/std features)
         xor     eax, eax                      // CPUID function #0
         cpuid                                 // largest std func/vendor tstring
         mov     dword ptr [vendor], ebx       // save
         mov     dword ptr [vendor+4], edx     //  vendor
         mov     dword ptr [vendor+8], ecx     //   tstring
         test    eax, eax                      // largest standard function==0?
         jz      $no_standard_features         // yes, no standard features func
         or      [CpuFeatureFlags], FEATURE_STD_FEATURES// does have standard features

         // Step 3: Get standard feature flags and signature
         mov     eax, 1                        // CPUID function #1
         cpuid                                 // get signature/std feature flgs
         mov     [signature], eax              // save processor signature

         // Step 4: Extract desired features from standard feature flags
         // Check for time stamp counter support
         mov     ecx, CPUID_STD_TSC            // bit 4 indicates TSC support
         and     ecx, edx                      // supports TSC ? CPUID_STD_TSC:0
         neg     ecx                           // supports TSC ? CY : NC
         sbb     ecx, ecx                      // supports TSC ? 0xffffffff:0
         and     ecx, FEATURE_TSC              // supports TSC ? FEATURE_TSC:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Check for MMX support
         mov     ecx, CPUID_STD_MMX            // bit 23 indicates MMX support
         and     ecx, edx                      // supports MMX ? CPUID_STD_MMX:0
         neg     ecx                           // supports MMX ? CY : NC
         sbb     ecx, ecx                      // supports MMX ? 0xffffffff:0
         and     ecx, FEATURE_MMX              // supports MMX ? FEATURE_MMX:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Check for CMOV support
         mov     ecx, CPUID_STD_CMOV           // bit 15 indicates CMOV support
         and     ecx, edx                      // supports CMOV?CPUID_STD_CMOV:0
         neg     ecx                           // supports CMOV ? CY : NC
         sbb     ecx, ecx                      // supports CMOV ? 0xffffffff:0
         and     ecx, FEATURE_CMOV             // supports CMOV ? FEATURE_CMOV:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Check support for P6-style MTRRs
         mov     ecx, CPUID_STD_MTRR           // bit 12 indicates MTRR support
         and     ecx, edx                      // supports MTRR?CPUID_STD_MTRR:0
         neg     ecx                           // supports MTRR ? CY : NC
         sbb     ecx, ecx                      // supports MTRR ? 0xffffffff:0
         and     ecx, FEATURE_P6_MTRR          // supports MTRR ? FEATURE_MTRR:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Check for initial SSE support. There can still be partial SSE
         // support. Step 9 will check for partial support.
         mov     ecx, CPUID_STD_SSE            // bit 25 indicates SSE support
         and     ecx, edx                      // supports SSE ? CPUID_STD_SSE:0
         neg     ecx                           // supports SSE ? CY : NC
         sbb     ecx, ecx                      // supports SSE ? 0xffffffff:0
         and     ecx, (FEATURE_MMXEXT+FEATURE_SSEFP+FEATURE_SSE) // supports SSE ?
                                               // FEATURE_MMXEXT+FEATURE_SSEFP:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Check for SSE2 support. (TRB - Was not part of sample code)
         mov     ecx, CPUID_STD_SSE2            // bit 26 indicates SSE2 support
         and     ecx, edx                      // supports SSE2 ? CPUID_STD_SSE2:0
         neg     ecx                           // supports SSE2 ? CY : NC
         sbb     ecx, ecx                      // supports SSE2 ? 0xffffffff:0
         and     ecx, (FEATURE_SSE2)           // supports SSE2 ?
                                               // FEATURE_SSE2:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Step 5: Check for CPUID extended functions
         mov     eax, 0x80000000               // extended function 0x80000000
         cpuid                                 // largest extended function
         cmp     eax, 0x80000000               // no function > 0x80000000 ?
         jbe     $no_extended_features         // yes, no extended feature flags
         or      [CpuFeatureFlags], FEATURE_EXT_FEATURES// does have ext. feature flags

         // Step 6: Get extended feature flags
         mov     eax, 0x80000001               // CPUID ext. function 0x80000001
         cpuid                                 // EDX = extended feature flags

         // Step 7: Extract vendor independent features from extended flags
         // Check for 3DNow! support (vendor independent)
         mov     ecx, CPUID_EXT_3DNOW          // bit 31 indicates 3DNow! supprt
         and     ecx, edx                      // supp 3DNow! ?CPUID_EXT_3DNOW:0
         neg     ecx                           // supports 3DNow! ? CY : NC
         sbb     ecx, ecx                      // supports 3DNow! ? 0xffffffff:0
         and     ecx, FEATURE_3DNOW            // support 3DNow!?FEATURE_3DNOW:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         // Step 8: Determine CPU vendor
         lea     esi, vendorAMD                // AMD's vendor tstring
         lea     edi, vendor                   // this CPU's vendor tstring
         mov     ecx, 12                       // strings are 12 characters
         cld                                   // compare lowest to highest
         repe    cmpsb                         // current vendor str == AMD's ?
         jnz     $not_AMD                      // no, CPU vendor is not AMD

         // Step 9: Check AMD specific extended features
         mov     ecx, CPUID_EXT_AMD_3DNOWEXT   // bit 30 indicates 3DNow! ext.
         and     ecx, edx                      // 3DNow! ext?
         neg     ecx                           // 3DNow! ext ? CY : NC
         sbb     ecx, ecx                      // 3DNow! ext ? 0xffffffff : 0
         and     ecx, FEATURE_3DNOWEXT         // 3DNow! ext?FEATURE_3DNOWEXT:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

         test    [CpuFeatureFlags], FEATURE_MMXEXT      // determined SSE MMX support?
         jnz     $has_mmxext                   // yes, don't need to check again

         // Check support for AMD's multimedia instruction set additions

         mov     ecx, CPUID_EXT_AMD_MMXEXT     // bit 22 indicates MMX extension
         and     ecx, edx                      // MMX ext?CPUID_EXT_AMD_MMXEXT:0
         neg     ecx                           // MMX ext? CY : NC
         sbb     ecx, ecx                      // MMX ext? 0xffffffff : 0
         and     ecx, FEATURE_MMXEXT           // MMX ext ? FEATURE_MMXEXT:0
         or      [CpuFeatureFlags], ecx                 // merge into feature flags

$has_mmxext:

         // Step 10: Check AMD-specific features not reported by CPUID
         // Check support for AMD-K6 processor-style MTRRs
         mov     eax, [signature]   // get processor signature
         and     eax, 0xFFF         // extract family/model/stepping
         cmp     eax, 0x588         // CPU < AMD-K6-2/CXT ? CY : NC
         sbb     edx, edx       // CPU < AMD-K6-2/CXT ? 0xffffffff:0
         not     edx            // CPU < AMD-K6-2/CXT ? 0:0xffffffff
         cmp     eax, 0x600         // CPU < AMD Athlon ? CY : NC
         sbb     ecx, ecx       // CPU < AMD-K6 ? 0xffffffff:0
         and     ecx, edx       // (CPU>=AMD-K6-2/CXT)&&
                    // (CPU<AMD Athlon) ? 0xffffffff:0
         and     ecx, FEATURE_K6_MTRR   // (CPU>=AMD-K6-2/CXT)&&
                    // (CPU<AMD Athlon) ? FEATURE_K6_MTRR:0
         or      [CpuFeatureFlags], ecx         // merge into feature flags

         jmp     $all_done      // desired features determined

$not_AMD:
         // Extract features specific to non AMD CPUs (except SSE2 already done)
$no_extended_features:
$no_standard_features:
$all_done:
   }
#endif
}
